package com.symaster.common.animation;

import com.symaster.common.geom.entity.LineSegment;
import com.symaster.common.geom.entity.Point;
import com.symaster.common.geom.util.PointUtil;
import com.symaster.common.swing.FpsManager;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author yinmiao
 * @version 2022-11-08 16:07
 */
public final class Animation {

    public static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(50, 600, 3 * 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.DiscardOldestPolicy());
    private float accuracy = 0.000000001f; // 曲线计算精度
    private final Set<AnimationListener> animationListeners = new HashSet<>(); // 动画列表

    public Animation() {

    }

    public float getAccuracy() {
        return accuracy;
    }

    public void setAccuracy(float accuracy) {
        this.accuracy = accuracy;
    }

    /**
     * 执行动画组
     *
     * @param sequence 动画执行顺序
     * @param strategy 上一组未完成的动画处理策略
     * @param listener 动画队列
     */
    public void execution(Sequence sequence, Strategy strategy, AnimationListener... listener) {
        execution(sequence, strategy, null, listener);
    }

    /**
     * 执行动画组
     *
     * @param sequence       动画执行顺序
     * @param strategy       上一组未完成的动画处理策略
     * @param finishListener 所有动画执行完成事件
     * @param listener       动画队列
     */
    public void execution(Sequence sequence, Strategy strategy, FinishListener finishListener, AnimationListener... listener) {
        if (listener == null || listener.length == 0) {
            throw new NullPointerException();
        }

        // 停止所有动画
        if (strategy == Strategy.Stop) {
            for (AnimationListener animationListener : animationListeners) {
                if (animationListener.isExecution()) {
                    animationListener.stop(1);
                }
            }
        } else if (strategy == Strategy.StopAtCurrentPosition) {
            for (AnimationListener animationListener : animationListeners) {
                if (animationListener.isExecution()) {
                    animationListener.stop(2);
                }
            }
        }

        for (AnimationListener animationListener : listener) {
            animationListeners.add(animationListener);
            animationListener.setAnimation(this);
        }

        if (Sequence.SEQUENCE_SIMULTANEOUSLY == sequence) {
            int length = listener.length;
            final int[] finishCount = {0};
            for (AnimationListener animationListener : listener) {
                animationListener.execution((status) -> {
                    synchronized (finishCount) {
                        finishCount[0] += 1;
                        if (finishCount[0] >= length) {
                            if (finishListener != null) {
                                finishListener.finish(status);
                            }
                        }
                    }
                });
            }
        } else if (Sequence.SEQUENCE_TURN == sequence) {
            sequenceTurn(listener, 0, finishListener);
        } else if (Sequence.SEQUENCE_CYCLE == sequence) {
            sequenceCycle(listener, 0, finishListener);
        }
    }

    private void sequenceCycle(AnimationListener[] listener, int ind, FinishListener finishListener) {
        if (ind > listener.length - 1) {
            listener[0].execution((status) -> {
                if (status == 1) {
                    sequenceCycle(listener, 1, finishListener);
                }
            });
        } else {
            listener[ind].execution((status) -> {
                if (status == 1) {
                    sequenceCycle(listener, ind + 1, finishListener);
                }
            });
        }
    }

    private void sequenceTurn(AnimationListener[] listener, int ind, FinishListener finishListener) {
        if (ind > listener.length - 1) {
            return;
        }
        if (ind == listener.length - 1) {
            listener[ind].execution(finishListener);
        } else {
            listener[ind].execution((status) -> {
                if (status == 1) {
                    sequenceTurn(listener, ind + 1, finishListener);
                }
            });
        }
    }

    /**
     * 停止所有正在执行的动画
     */
    public void stop() {
        for (AnimationListener animationListener : animationListeners) {
            if (animationListener.isExecution()) {
                animationListener.stop(1);
            }
        }
    }

    /**
     * @param curve 曲线
     * @param time  时间 (0 - 1)
     * @return 返回当前位置
     */
    public double getPoint(Curve curve, double time) {
        List<LineSegment> lineSegments = curve.toLineSegments();
        Point startPointBus = lineSegments.get(0).getStartPoint();
        Point endPointBus = lineSegments.get(lineSegments.size() - 1).getEndPoint();

        if (startPointBus.getX() > accuracy || startPointBus.getY() < 1.0f - accuracy ||
                endPointBus.getX() < 1.0f - accuracy || endPointBus.getY() > accuracy) {
            lineSegments.add(0, new LineSegment(new Point(0, 0), startPointBus));
            lineSegments.add(new LineSegment(endPointBus, new Point(1, 0)));
        }

        List<LineSegment> lineSegments1 = new ArrayList<>();
        for (LineSegment lineSegment : lineSegments) {
            Point lineStartPoint = lineSegment.getStartPoint();
            Point lineEndPoint = lineSegment.getEndPoint();

            if (Math.abs(lineStartPoint.getX() - time) <= accuracy) {
                return lineStartPoint.getY();
            }

            if (Math.abs(lineEndPoint.getX() - time) <= accuracy) {
                return lineEndPoint.getY();
            }

            double max = Math.max(lineStartPoint.getX(), lineEndPoint.getX());
            double min = Math.min(lineStartPoint.getX(), lineEndPoint.getX());

            if (min < time + accuracy && max > time - accuracy) {
                lineSegments1.add(lineSegment);
            }
        }

        if (lineSegments1.isEmpty()) {
            return 1.0D;
        }

        LineSegment lineSegment1 = new LineSegment(new Point(time, -1), new Point(time, 2));
        for (LineSegment lineSegment : lineSegments1) {
            Point crossPoint = PointUtil.getCrossPoint(lineSegment, lineSegment1);
            if (crossPoint != null) {
                return 1.0 - crossPoint.getY();
            }
        }
        return 1.0D;
    }

    /**
     * 报告动画执行完成
     */
    private void animationExecutionCompleted(AnimationListener animationListener) {

    }

    public static void main(String[] args) {
        // time:0.8927679999917746, p:0.021073

        Animation animation = new Animation();
        double point = animation.getPoint(Curve.TYPE_FAST_TO_SLOW, 0.8927679999917746);
        System.out.println(point);
    }

    public enum Sequence {
        SEQUENCE_TURN(0, "动画执行顺序(顺序执行)"),
        SEQUENCE_SIMULTANEOUSLY(1, "动画执行顺序(同时)"),
        SEQUENCE_CYCLE(2, "动画循环执行"),
        ;

        private final int code;
        private final String remark;

        Sequence(int code, String remark) {
            this.code = code;
            this.remark = remark;
        }

        public int getCode() {
            return code;
        }

        public String getRemark() {
            return remark;
        }
    }

    public enum Strategy {
        Stop, // 立刻停止所有动画
        NoOps, // 不执行任何操作
        StopAtCurrentPosition, // 停在当前位置
    }

    public interface FinishListener {
        // 停止状态, 1: 正常结束, 2: 强制停止
        void finish(int status);
    }

    public static class AnimationConfig {
        private final double startPoint; // 开始值
        private final double dstPoint; // 结束值
        private long time = 200; // 毫秒 (动画总执行时间)
        private int maxFps = 120; // 最大限制帧数 (最大每秒刷新次数)

        public AnimationConfig(double startPoint, double dstPoint) {
            this.startPoint = startPoint;
            this.dstPoint = dstPoint;
        }

        public static AnimationConfig ofTime(long time, double startPoint, double dstPoint) {
            AnimationConfig animationConfig = new AnimationConfig(startPoint, dstPoint);
            animationConfig.setTime(time);
            return animationConfig;
        }

        public long getTime() {
            return time;
        }

        public void setTime(long time) {
            this.time = time;
        }

        public double getStartPoint() {
            return startPoint;
        }

        public double getDstPoint() {
            return dstPoint;
        }

        public int getMaxFps() {
            return maxFps;
        }

        public void setMaxFps(int maxFps) {
            this.maxFps = maxFps;
        }
    }

    public static abstract class AnimationListener {

        // 动画管理(AnimationManage)对象
        private Animation animation;
        // 该动画的执行曲线
        private final Curve curve;
        // 该动画的配置
        private final AnimationConfig config;
        // 标记该动画是否可以继续执行
        private boolean run = true;
        // 标记该是否正在执行
        private boolean execution = false;
        // 标记是否已调用 dstPoint
        private boolean overPoint = false;
        // 1:停止在结束位置; 2:停止在当前位置
        private int stopMethod = 1;

        private final FpsManager fpsManager;

        public AnimationListener(Curve curve, AnimationConfig config) {
            this.curve = curve;
            this.config = config;
            this.fpsManager = new FpsManager();
        }

        public void setAnimation(Animation animation) {
            this.animation = animation;
        }

        public void execution(FinishListener finishListener) {
            initStatus();
            execute(() -> {
                execution = true;
                handler(config.getStartPoint());
                double startTime = System.nanoTime() / 1000000.0;

                // int maxFps = config.getMaxFps();
                // double refreshInterval = 1000.0 / (double) maxFps; // 至少间隔多少毫秒可以执行

                try {
                    // int count = 0;
                    int status = 2;

                    while (run) {
                        double thatTime = System.nanoTime() / 1000000.0;
                        double passTime = thatTime - startTime;

                        // if (count > 0) {

                        if (passTime >= config.time) {
                            handler(config.getDstPoint());
                            overPoint = true;
                            status = 1;
                            break;

                        } else if (passTime > 0){
                            double time = passTime / config.time;
                            double point = animation.getPoint(curve, time);
                            // if (count == 1 || count == 2) {
                            //     System.out.printf("%s; %.6f%n", count, time);
                            // }

                            double thatPoint = point * (config.getDstPoint() - config.getStartPoint()) + config.getStartPoint();

                            // System.out.printf("time:%s, p:%s%n", time, thatPoint);
                            handler(thatPoint);
                        }

                        fpsManager.paintWait(config.maxFps);
                        // }
                        /*else {
                            handler(config.getStartPoint());
                        }*/

                        // double completionTime = System.nanoTime() / 1000000.0;
                        // double v1 = completionTime - thatTime; // 这次执行的总时间开销(毫秒)
                        // double v2 = refreshInterval - v1; // 需要等待多少毫秒可以开始下次执行, 如果为负数则说明这次执行时间开销过大, 需要立刻开始下次执行

                        // if (v2 > 0) {
                        //     // 转换成微妙等待, 这样精度较高
                        //     long round = Math.round(v2 * 1000.0);
                        //     TimeUnit.MICROSECONDS.sleep(round);
                        // }


                        // double t1 = System.nanoTime() / 1000000.0;

                        // double t2 = System.nanoTime() / 1000000.0;
                        // if (count == 0 || count == 1 || count == 2) {
                        //     System.out.printf("%s; %.4f%n", count, t2 - t1);
                        // }

                        // count++;
                    }

                    if (!overPoint && stopMethod == 1) {
                        handler(config.getDstPoint());
                        overPoint = true;
                    }

                    if (finishListener != null) {
                        finishListener.finish(status);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    execution = false;
                    animation.animationExecutionCompleted(this);
                }
            });
        }

        private void initStatus() {
            run = true;
            overPoint = false;
        }

        private void execute(Runnable command) {
            threadPoolExecutor.execute(command);
        }

        public boolean isExecution() {
            return execution;
        }

        public void stop(int stopMethod) {
            this.stopMethod = stopMethod;
            if (run) {
                run = false;
            }
        }

        public abstract void handler(double factor);
    }
}
